package mkv.data 
{
	/**
	 * A Microformat is a single line mapping for converting string data to named key=>value pairs
	 * e.g. the microformat:
	 * new Microformat("[name], [description], [graphicid], [typeid], [value], [bonustype]");
	 * 
	 * can map:
	 * "production, Reduces chemical poisoning in the hive, ChemicalPoison, ChemicalPoison, -0.1, ADD_MULTIPLY"
	 * 
	 * to:
	 * data: {
	 *   name: "production",
	 *   description: "Reduces chemical poisoning in the hive",
	 *   graphicid: "ChemicalPoison",
	 *   typeid: "ChemicalPoison",
	 *   value: -0.1,
	 *   bonustype: "ADD_MULTIPLY"
	 * }
	 * 
	 * @author John Beech
	 */
	public class Microformat
	{
		/** The identifier to apply this microformat to */
		public var match:String;
		/** The pattern that defines this microformat */ 
		public var micropattern:String;
		/** A list of the tokens constructed from the microformat */
		public var tokens:Array;
		/** A list of keys and the order they occur in the microformat */
		public var properties:Array;
		/** A regex constructed from the microformat used to deserialize data */
		public var regex_match:RegExp;
		/** The marker object used for highligting properties */
		protected var VALUE_MARKER:Object;
		
		public function Microformat(pattern:String, match:String) {
			this.micropattern = pattern;
			this.match = match;
			
			ParseTokens(pattern);
			
			if (FFONSerializer.VERBOSE_TRACE) {
				trace("Created microformat for", match, " [" + properties + "]");
			}
		}
		
		/**
		 * Returns a list of tokens defined in the microformat
		 * - Looks for keys, in the format: [KEY]
		 * - Ignores white space
		 * - Keeps a list of expected filler tokens
		 * Example pattern formats:
		 * [surname], [firstname], [dob];
		 * @param	pattern
		 */
		protected function ParseTokens(pattern:String):void {
			var token:*;
			var c:String;
			VALUE_MARKER = {};
			
			tokens = [];
			properties = [];
			token = '';
			
			for (var i:int = 0; i < pattern.length; i++) {
				c = pattern.charAt(i);
				if (c == '[') {
					if (token != '') {
						tokens.push(Microformat.trim(token));
					}
						
					/* Reset token */
					token = '';
				}
				else if (c == ']') {
					if (token != '') {
						properties.push(token);
						tokens.push(VALUE_MARKER);
						
						/* Reset token */
						token = '';
					}
				}
				else if (c == ' ' || c == "\t" || c == "\n" || c == "\r") {
					/* Include white space for the moment */
					token = token + c;
				}
				else {
					token = token + c;
				}
			}
				
			/* Add final token at end of pattern */
			if (token != '') {
				tokens.push(token);
			}
				
			/* Create regular expression for matching values */
			var match_construct:Array = [];
			
			for each(token in tokens) {
				if (token == VALUE_MARKER) {
					match_construct.push('(.*)');
				}
				else {
					match_construct.push(token);
				}
			}
			
			regex_match = new RegExp(match_construct.join(''), '');
		}
		
		/**
		 * Applies the regex for this microformat to match useful values from the input data
		 * @param	dataline  a compatable line
		 * @return  an object containing keys  defined by the microformat, i.e. indexed by the properties array
		 */
		public function Deserialize(dataline:String):Object {
			var result:Object = { };
			
			var matches:Array = dataline.match(regex_match);
			var c:uint = 1;
			
			if (matches == null) {
				if (FFONSerializer.VERBOSE_TRACE) {
					trace("WARNING: Match failed for microformat on line: " + match + ": " + dataline);
					trace(" Regex: " + regex_match);
				}
				return dataline;
			}
			
			for each(var key:String in properties) {
				result[key] = Microformat.trim(matches[c]);
				c++;
			}
			
			return result;
		}
		
		public function Serialize(object:*):String {
			var result:String = '';
			var key:String;
			
			if (object is String) {
				return object;
			}
			
			var n:uint = 0;
			for each(var token:* in tokens) {
				if (token == VALUE_MARKER) {
					key = properties[n];
					if (object.hasOwnProperty(key)) {
						result += object[key];
					}
					n++;
				}
				else {
					result += token;
				}
			}
			
			return result;
		}
		
		/**
		 * Modify a data object to include a new property.
		 * If property already exists, change it into an array and list the values
		 * @param	data      the data object to be modified
		 * @param	section   the section to add the property to
		 * @param	key       the property name (key)
		 * @param	value     the propery value (value)
		 */
		protected static function AddProperty(path:Object, key:String, value:*):void {
			var path:*;
			
			if (path.hasOwnProperty(key) == false) {
				/* Assign new property */
				path[key] = value;
			}
			else if (path[key] is Array) {
				/* Append new property */
				path[key].push(value);
			}
			else {
				/* Convert existing property to array and append new value */
				path[key] = [path[key]];
				path[key].push(value);
			}
		}
		
		protected static function trim(s:String):String {
			return s.replace(/^\s+|\s+$/gs, '');
		}
	}

}